/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
**     of its contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "mouse.h"
#include <QGraphicsScene>
#include <QPainter>
#include <QStyleOption>
#include <QGraphicsItem>
#include <new>
#include <string>
#include <math.h>
#include <iostream>

using namespace std;

static const double Pi = 3.14159265358979323846264338327950288419717;
static double TwoPi = 2.0 * Pi;

static qreal normalizeAngle(qreal angle)
{
    while (angle < 0)
        angle += TwoPi;
    while (angle > TwoPi)
        angle -= TwoPi;
    return angle;
}

//Constructor
Mouse::Mouse()
    :  power((qrand()%7) + 1), angle(0), mouseEyeDirection(0), maxHealth(10+qrand()%140)
{   fight = false;
    health = maxHealth;
    alive = true;
    setRotation(qrand() % (360 * 16));
}

QRectF Mouse::boundingRect() const
{
    qreal adjust = 0.5;
    return QRectF(-18 - adjust, -22 - adjust,
                  36 + adjust, 60 + adjust);
}

QPainterPath Mouse::shape() const
{
    QPainterPath path;
    path.addRect(-5 - 1*power, -20, 10 + 2*power, 40);
    return path;
}

//visualizing mouse in the scene
void Mouse::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    //if mouse is dead set alivebool to false, else set color based on health/maxHealth ratio
    if(this->health<0)
        this->alive=false;
    else{
        qreal val = health/maxHealth;
        int value= val  *255;
        color.setRgb(255,value,value);
    }

    // Body - visual size is based on power
    painter->setBrush(color);
    painter->drawEllipse(-5 - 1*power, -20, 10 + 2*power, 40);

    // Eyes
    painter->setBrush(Qt::white);
    painter->drawEllipse(-10, -17, 8, 8);
    painter->drawEllipse(2, -17, 8, 8);

    // Nose
    painter->setBrush(Qt::black);
    painter->drawEllipse(QRectF(-2, -22, 4, 4));

    // Pupils
    painter->drawEllipse(QRectF(-8.0 + mouseEyeDirection, -17, 4, 4));
    painter->drawEllipse(QRectF(4.0 + mouseEyeDirection, -17, 4, 4));

    // Ears
    QColor color;
    //if it is dealing damage/fighting its ears are red
    //if its last action is fleeing its ears are gray
    if(fight){
        color = Qt::red;
    }
    else if (action<3&&action>0){
        color = Qt::lightGray;
    }else{
        color = Qt::darkYellow;
    }

    painter->setBrush(color);
    painter->drawEllipse(-17, -12, 16, 16);
    painter->drawEllipse(1, -12, 16, 16);

    // Tail
    QPainterPath path(QPointF(0, 20));
    path.cubicTo(-5, 22, -5, 22, 0, 25);
    path.cubicTo(5, 27, 5, 32, 0, 30);
    path.cubicTo(-5, 32, -5, 42, 0, 35);
    painter->setBrush(Qt::NoBrush);
    painter->drawPath(path);
}
//causes mouse to perform movemet
void Mouse::advance(int step){ 
    //removes mouse from scene if it is dead
    if (!alive){
        cout << this << " is dead" << endl;
        scene()->removeItem(this);
        return;
    }
    if (!step)
        return;

    // Keeps mouse roaming unless it is out of bounds.
    QLineF lineToCenter(QPointF(0, 0), mapFromScene(0, 0));
    if (lineToCenter.length() > 150) {
        //dont roam.
    }
    else if (::sin(angle) < 0) {
        angle += 0.25;
    } else if (::sin(angle) > 0) {
        angle -= 0.25;}

    // Register other mice and perform an action
    QList<QGraphicsItem *> dangerMice = scene()->items(QPolygonF()
                                                       << mapToScene(0, 0)
                                                       << mapToScene(-400, -400)
                                                       << mapToScene(400, -400));
    int closestMouse = -1;
    qreal lengthToClosestMouse;

    foreach (QGraphicsItem *item, dangerMice) {
        if (item == this) continue;

        QLineF lineToMouse(QPointF(0, 0), mapFromItem(item, 0, 0));

        // finds the mouse that is closest to this mouse
        if(closestMouse==-1||lengthToClosestMouse>lineToMouse.length()){
            closestMouse = dangerMice.indexOf(item);
            lengthToClosestMouse = lineToMouse.length();
        }
    }
    if(closestMouse!=-1){
        QLineF lineToMouse(QPointF(0, 0), mapFromItem(dangerMice.at(closestMouse), 0, 0));
        qreal angleToMouse = ::acos(lineToMouse.dx() / lineToMouse.length());
        if (lineToMouse.dy() < 0)
            angleToMouse = TwoPi - angleToMouse;
        angleToMouse = normalizeAngle((Pi - angleToMouse) + Pi / 2);

        if (angleToMouse >= 0 && angleToMouse < Pi / 2) {
            // Rotate right
            angle -= 0.5;
        } else if (angleToMouse <= TwoPi && angleToMouse > (TwoPi - Pi / 2)) {
            // Rotate left
            angle += 0.5;
        }
    }

    //if it sees a mouse -> blackbox the action.
    if(closestMouse!=-1){
        QGraphicsItem * item = dangerMice.at(closestMouse);
        nearMouse = static_cast<Mouse *>(item);
        action =  blackBoxAction(nearMouse);
    }else{
        action = 0;
        nearMouse = NULL;
    }

    //this mouse sees a mouse and it collides with the closest one, it will behave violently
    if(closestMouse!=-1 &&  this->collidesWithItem(dangerMice.at(closestMouse))){

        if(!fight){
            initiative = qrand()%3;
        }
        fight=true;
        if(nearMouse->nearMouse!=this){

            //if the mouse does not see me, i get a free attack of opurtunity
            nearMouse->health -= ((power + qrand()%7)/5);
        } else {

            //"you sir! I do propose a duel."
            if(initiative<nearMouse->initiative){

            }else if(initiative>nearMouse->initiative || initiative <= 0){

                if (initiative<=0) {
                    nearMouse->dealDamage(((power + qrand()%7)/5));
                    nearMouse->initiative =0;
                    initiative = 3;
                }else{

                    initiative-= 1;
                }
            }else{
                initiative = qrand()%3;
            }
        }

        if(nearMouse->health<=0){
            this->kills++;
            cout <<this<< " KILL!" << this->kills << endl;
            fight=false;
        }
    }else{
        fight=false;
    }

    qreal dx = ::sin(angle) * 10;
    mouseEyeDirection = (qAbs(dx / 5) < 1) ? 0 : dx / 5;
    //if the mouse is out of bounds it will rotate towards the center
    if((lineToCenter.length() > 150 + (35 - power*5)) && action<6.5){
        QPolygonF polygon;
        polygon << mapToScene(0, 0) << mapToScene(-50, -400) << mapToScene(50, -400);
        if(polygon.containsPoint(QPointF(0,0), Qt::FillRule())){
            setRotation(rotation());
        }else{
            if(lineToCenter.angle()<270 && lineToCenter.angle()>90){
                setRotation(rotation() - 10);

            }else{
                setRotation(rotation() + 10);
            }
        }

    }// perform attack rotate to enemy
    else if(action >= 6.5){
        setRotation(rotation() + dx);
    }else{
        setRotation(rotation() - dx);
    }
    //perform wait
    if((action>3.5 && action<6.5)  || (closestMouse!=-1 && this->collidesWithItem(dangerMice.at(closestMouse)))){
        setPos(mapToParent(0, 0));
        if(closestMouse==-1){
            setPos(mapToParent(0, - blackBoxSpeed()));
        }
    }else{
        //perform flee
        setPos(mapToParent(0, - blackBoxSpeed()));
    }
}

double Mouse::blackBoxAction(Mouse *item){

    //calculates degree of each linguistic variable for power
    double weak = fuzzyfyWeak(item->power);
    double neutral = fuzzyfyNeutral(item->power);
    double strong = fuzzyfyStrong(item->power);

    //calculates degree of each linguistic variable for health
    double low = fuzzyfyHealthLow();
    double medium = fuzzyfyHealthMedium();
    double high = fuzzyfyHealthHigh();
    //calculates the degree of mouse being a killer
    double killer = hasBloodthirst();

    //calculates probability of mouse attacking
    double attack;
    if((high>0&&strong==0)||(medium>0&&weak>0)||(killer>0 || isVery(weak)>0.2)){
        attack = high*neutral + high*weak + medium*weak + killer + isVery(weak);
    }else{
        attack =  0;
    }
    //calculatse probability of mouse waiting
    double wait;
    if((high>0&&strong>0)||(neutral>0&&medium>0)||(low>0&&weak>0)){
        wait = high*strong +neutral * medium + low*weak;
    }else{
        wait = 0;
    }
    //calculates probability of mouse fleeing
    double flee;
    if((medium>0&&strong>0)||(low>0&&weak==0)||(isSlightly(strong)>0.3)){
        flee = medium*strong + low*neutral + low*strong + isSlightly(strong);
    }else{
        flee = 0;
    }
    double actionC =((0+1+2+3)*flee+ (4+5+6+7)*wait+(8+9+10+11)*attack)/(4*flee+4*wait+4*attack);

    return actionC;
}

//returns probability of mouse being weak
double Mouse::fuzzyfyWeak(qreal pow){
    double val = pow;
    double weak = 1.333-0.334*val;
    if(weak<0.0)
        weak = 0;
    return weak;
}

//returns probability of mouse having neutral power
double Mouse::fuzzyfyNeutral(qreal pow){
    double val = pow;
    double neutral = 0;
    if(val<4){
        neutral = -0.333 + 0.333*val;
    }else{
        neutral = 2.329 - 0.334*val;
    }
    if(neutral<0){
        neutral = 0;
    }
    return neutral;
}

//returns probability of mouse being strong
double Mouse::fuzzyfyStrong(qreal pow){
    double val = pow;
    double strong = -1.334 + 0.333*val;
    if(strong<0){
        strong=0;
    }
    return strong;
}

//returns probability of health being low
double Mouse::fuzzyfyHealthLow(){
    double val = this->health;
    double low = 2.6-0.04*val;
    if(low>1.0){
        low = 1.0;
    }else if(low<0.0){
        low = 0.0;
    }
    return low;
}

//returns probability of health being medium
double Mouse::fuzzyfyHealthMedium(){
    double val = this->health;
    double medium;
    if(val>75){
        medium = 4.4 -0.04*val;
    }else{
        medium = -1.6+0.04*val;
    }

    if(medium>1.0){
        medium = 1.0;
    }else if(medium<0.0){
        medium = 0.0;
    }
    return medium;
}
//returns probability of health being high
double Mouse::fuzzyfyHealthHigh(){
    double val = this->health;
    double high = -3.44 + 0.04*val;
    if(high>1.0){
        high = 1.0;
    }else if(high<0.0){
        high = 0.0;
    }
    return high;
}

//returns the center of gravity for possibilities of action for this mouse
double Mouse::blackBoxSpeed(){
    double weak = fuzzyfyWeak(this->power);
    double neutral = fuzzyfyNeutral(this->power);
    double strong = fuzzyfyStrong(this->power);

    double low = fuzzyfyHealthLow();
    double medium = fuzzyfyHealthMedium();
    double high = fuzzyfyHealthHigh();

    double killer = hasBloodthirst();

    //calculates probability of mouse moving fast
    double fast;
    if((high>0&&weak==0)||(medium>0&&strong>0)||(killer>0||isExtremely(strong))){
        fast = high * neutral + high * strong + medium * strong + killer + isExtremely
                (strong);
    }else{
        fast =  0;
    }
    //calculates probability of mouse moving normal speed
    double normal;
    if((low>0&&strong>0)||(medium>0&&neutral>0)||(high>0&&weak>0)){
        normal =neutral*strong + low * strong + neutral * medium + high * weak;
    }else{
        normal = 0;
    }
    //calculates probability of mouse moving slow
    double slow;
    if((low>0&&strong==0)||(medium>0&&weak>0) || isVeryVery(weak)>0.5){
        slow = low * weak  + low * neutral + medium * weak + isVeryVery(weak);
    }else{
        slow = 0;
    }

    double speed = (2 * slow + 3 * normal + 4 * fast)/(slow+normal+fast);
    return speed;
}

//the grade of kill with this mouse
double Mouse::hasBloodthirst(){
    double killer = 0.333333333334*this->kills;
    if (killer>=1){
        return 1;
    }
    return killer;
}
//hedge methods
double Mouse::isSlightly(double val){
    double slightly = pow(val,1.3);
    return slightly;
}
double Mouse::isVery(double val){
    double very=pow(val,1.7);
    return very;
}
double Mouse::isExtremely(double val){
    double extremely=pow(val,2);
    return extremely;
}
double Mouse::isVeryVery(double val){
    double veryVery=pow(val,4);
    return veryVery;
}

/**
 * @brief method to be used on this mouse if it is in a proper fight. it will take the damage and return damage and count kill.
 * @param damage to deal
 */
void Mouse::dealDamage(qreal damage){
    this->health -= damage;
}



